from datetime import date, datetime
from ft_utils import *
from ft_table import FtTable
import model


def forceNumber(text):
    try:
        v = int(text)
    except:
        v = float(text)
        
    return v


class DLZError(Exception):
    """Exception base class for errors in Trac."""

    def __init__(self, message, title=None, show_traceback=False):
        """If message is a genshi.builder.tag object, everything up to the
        first <p> will be displayed in the red box, and everything after will
        be displayed below the red box.
        If title is given, it will be displayed as the large header above the
        error message.
        """
        Exception.__init__(self, message)
        self._message = message
        self.title = title
        self.show_traceback = show_traceback

    message = property(lambda self: self._message, 
                       lambda self, v: setattr(self, '_message', v))

    def __unicode__(self):
        return unicode(self.message)

class DLZ_Duplicate_Error(DLZError):
    pass

class DLZ_Not_Found_Error(DLZError):
    pass


class Record(object):
    # 0.11 compatibility
    #use the data_dictionary for pre-defined fields
    #but it does accept new fields by doing TableRecord['new field']
    time_created = property(lambda self: self.values.get('CREATED_DATE'))
    time_changed = property(lambda self: self.values.get('MODIFIED_DATE'))
    
    def __init__(self, ft, rowid=None, change_table_id=None):
        self.ft = ft
        self.fields = ['rowid']
        self.protected_fields = []
        self.time_fields = []
        self.number_fields = []
        self.rowid = rowid
        self.change_table_id = change_table_id
        for fld in ft.fields():
            self.fields.append(fld['name'])
            if fld['type']=='datetime':
                self.time_fields.append(fld['name'])
            if fld['type']=='number':
                self.number_fields.append(fld['name'])
        self.values = {} #keep all the value in it
        if rowid is not None:
            self._fetch_record(rowid)
        else:
            self._init_defaults()
            self.id = None
        self._old = {}#keep old value


    exists = property(fget=lambda self: self.id is not None)

    def _init_defaults(self):
        pass

    def SqlQuery(self, sql):
        return self.ft.SqlQuery(sql)
    
    def _fetch_record(self, key):
        
        sql = "select %s from %s where %s = '%s' " % (','.join(['ROWID'] + self.fields),self.ft.tableid, 'rowid', key)
        response = self.ft.SqlQuery(sql)
        
        if len(response)==0:
            raise DLZ_Not_Found_Error(message = 'TableRecord %(id)s does not exist.' % {"id": key},title='Invalid record key')
        if len(response)>1:
            raise DLZ_Duplicate_Error(message = 'TableRecord %(id)s has more than one records.' % {"id": key},title='Duplicate Key')
        row = response[0]

        self.id = key
        self.rowid = int(row.get('rowid'))
        for fld in self.fields:
            if row.has_key(fld):
                self.values[fld]= row.get(fld)
            else:
                self.values[fld]= None

    def __getitem__(self, name):
        return self.values.get(name)

    def __setitem__(self, name, value):
        """Log ticket modifications so the table ticket_change can be updated
        """
        if name in self.values and self.values[name] == value:
            return
        if name not in self._old: # Changed field
            #if it is a new value, return None
            self._old[name] = self.values.get(name)
        elif self._old[name] == value: # Change of field reverted
            del self._old[name]
        self.values[name] = value

    def get_value_or_default(self, name):
        """Return the value of a field or the default value if it is undefined
        """
        try:
            value = self.values[name]
            if not value:
                value = None
            return value
            
        except KeyError:
            pass
        
    def populate(self, values):
        """Populate the ticket with 'suitable' values from a dictionary"""
        for fld in self.fields:
            self[fld] = values.get(fld, None)


    def insert(self, t=None):
        """Add ticket to database.
        
        The `db` argument is deprecated in favor of `with_transaction()`.
        """
        assert not self.exists, 'Cannot insert an existing ticket'

        # Add a timestamp
        # Perform type conversions
        values = dict(self.values)
        for field in self.time_fields:
            if field in values:
                values[field] = str(values[field])#do the conversion
        rowid = self.ft.insert(values, t)[0]['rowid']
        self.id = int(rowid)
        self.rowid = int(rowid)
        self.values['rowid'] = int(rowid)
        self._old = {}

#        for listener in TicketSystem(self.env).change_listeners:
#            listener.ticket_created(self)

        return self.id
    
    def save_changes(self, changes):
        if self.change_table_id:
            ft = FtTable(None, None, self.change_table_id)
            return ft.batch_insert(changes)

    def update(self, t=None):
        """
        Store ticket changes in the database. The ticket must already exist in
        the database.  Returns False if there were no changes to save, True
        otherwise.
        
        The `db` argument is deprecated in favor of `with_transaction()`.
        """
        assert self.exists, 'Cannot update a new ticket'

        if not self._old:
            return False # Not modified

        #save the change log
        changes = {}
        batch_changes = []
        for name in self._old.keys():
            if name in self.fields:
                tableid = self.ft.tableid
                field_name = name
                timestamp = str(datetime.now())
                oldvalue = self._old[name]
                newvalue = self.values[name]
                changes[name] = newvalue
                batch_changes.append({'tableid': tableid, 'field_name': field_name, 
                    'time_stamp': timestamp, 'old_value': oldvalue, 'rowid': self.rowid,
                    'new_value': newvalue, 'action': 'update'})
                
        if changes:
            self.ft.update(changes, self.rowid, t)
        if batch_changes:
            self.save_changes(batch_changes)
        
        self._old = {}
        return True

    def get_changelog(self):
        """Return the changelog as a list of tuples of the form
        (time, author, field, oldvalue, newvalue, permanent).

        While the other tuple elements are quite self-explanatory,
        the `permanent` flag is used to distinguish collateral changes
        that are not yet immutable (like attachments, currently).
        """
        ft = FtTable(None, None, self.change_table_id)
        sql = "select * from %s where tableid=%s and rowid='%s'" % (self.ft.tableid, self.rowid)
        return ft.SqlQuery(sql)
        

    def delete(self, t=None):
        """Delete the ticket.
        
        The `db` argument is deprecated in favor of `with_transaction()`.
        """
        return self.ft.SqlQuery("delete from %s where rowid='%s'" % (self.ft.tableid, self.rowid), t)
        



#        for listener in TicketSystem(self.env).change_listeners:
#            listener.ticket_deleted(self)
    def validate(self):
        pass
#        """
#        Not working yet, need more thoughs on it. 
#        To be more flexible, I should pass python code as validator.
#        """
#        msg = {}
#        record = {}
#        isvalid = True
#        for k, v in self.values:
#            validator = self.data_dictionary[k].validator
#            if validator=='':
#                pass
#            else:
#                exec(validator)
#                vresult = custom_validator(self.values)
#                if vresult['valid']:
#                    pass
#                else:
#                    isvalid = False
#                    msg[k] = vresult['msg']
#                    record[k] = vresult['newvalue']
#        return {'valid': isvalid, 'record': record, 'msg': msg}
#            
#            


            
            


def test():
    print 'retrieve an existing record'
    tr = Record('compaction', 108)
    print 'test location:',tr['test_location']
    
    print 'create a new record'
    
    tr = Record('compaction')
    tr['ID'] = 2001
    tr['test_location'] = 'Mel is testing this thing'
    try:
        tr.insert()
    except DLZ_Duplicate_Error:
        print 'record already exist'
        tr = Record('compaction', 2001)
        print 'test location:',tr['test_location']
    
    print 'delete a new record'
    try:
        tr = Record('compaction', 2001)
    except DLZ_Not_Found_Error:
        print 'record not found'
    print 'access the record: test location:',tr['test_location']
    
    tr.delete()
    
#    print 'check if it is deleted, should get a error message'
#    tr = Record('compaction', 2001)
    
    print 'testing making changes'
    tr = Record('compaction')
    tr['ID'] = 2001
    tr['test_location'] = 'Mel is testing this thing'
    tr.insert()
    print 'test location:',tr['test_location']
    tr['test_location'] = 'Mel is testing another thing'
    tr.save_changes();
    print 'retrieve the changes'
    tr = Record('compaction', 2001)
    print 'test location:',tr['test_location']
    
    
